/*
 *  FNT.cpp
 *  SCMS
 *
 *  Created by poiuy_qwert on 14/07/09.
 *
 */

#include "FNT.h"
#include "BMP.h"

#include <list>

FNT::FNT(vector<unsigned char*> letters, char width, char height, char low) {
	this->close();
	this->letters.assign(letters.begin(), letters.end());
	this->width = width;
	this->height = height;
	this->lowIndex = low;
}

void FNT::open_file(const char *filename) {
	ifstream file(filename, ios::binary);
	if (!file.is_open()) {
		SCMSError err("Open", "Could not open file '%s'", filename);
		throw err;
	}
	file.seekg(0, ios::end);
	int size = file.tellg();
	file.seekg(0);
	char *buffer = new char[size];
	file.read(buffer, size);
	file.close();
	try {
		this->open_data(buffer, size);
	} catch (exception) {
		delete [] buffer;
		throw;
	}
	delete [] buffer;
}

void FNT::open_data(const char *buffer, int size) {
	FNT_HEADER header;
	unpack(buffer, sizeof(FNT_HEADER), "<[8C]", &header);
	if (memcmp(header.sign, "FONT", 4)) {
		SCMSError err("Open", "Invalid FNT file (invalid signature)");
		throw err;
	}
	this->letters.clear();
	const char *offsets = buffer+sizeof(FNT_HEADER);
	unsigned long offset;
	for (int n = 0; n <= header.highIndex-header.lowIndex; n++) {
		unpack(offsets, 4, "<L", &offset);
		unsigned char *letter = new unsigned char[header.maxHeight*header.maxWidth];
		memset(letter, 0, header.maxHeight*header.maxWidth);
		if (offset) {
			FNT_CHR_HEADER chr;
			unpack(buffer+offset, sizeof(FNT_CHR_HEADER), "<[4C]", &chr);
			const char *comp = buffer+offset+sizeof(FNT_CHR_HEADER);
			int pos = 0;
			while (pos < chr.height*chr.width) {
				pos += (*comp & 0xF8) >> 3;
				letter[(chr.yOffset+(pos/chr.width))*header.maxWidth+chr.xOffset+(pos%chr.width)] = *comp & 0x07;
				pos++;
				comp++;
			}
		}
		this->letters.push_back(letter);
		offsets += sizeof(long);
	}
	this->lowIndex = header.lowIndex;
	this->width = header.maxWidth;
	this->height = header.maxHeight;
}

void FNT::export_file(const char *filename, Palette *palette) {
	BMP *bmp = this->export_data(palette);
	bmp->save_file(filename);
	delete bmp;
}

BMP* FNT::export_data(Palette *palette) {
	int fullWidth = this->width*(256-this->lowIndex);
	unsigned char *data = new unsigned char[fullWidth*this->height];
	memset(data, 0, fullWidth*this->height);
	for (int i = 0; i < 256-this->lowIndex; i++) {
		unsigned char *image = this->get_letter(i+this->lowIndex);
		for (int y = 0; y < this->height; y++) {
			memcpy(data+(fullWidth*y)+(this->width*i), image+(this->width*y), this->width);
		}
	}
	BMP *bmp = new BMP(data, fullWidth, this->height, palette);
	delete [] data;
	return bmp;
}

void FNT::save_file(const char *filename) {
	ofstream file(filename, ios::binary);
	if (!file.is_open()) {
		SCMSError err("Save", "Could not open file '%s'", filename);
		throw err;
	}
	int size;
	char *buffer = this->save_data(size);
	file.write(buffer, size);
	delete [] buffer;
	file.close();
}

char* FNT::save_data(int &size) {
#warning SWITCH TO PACK
	int offset = sizeof(FNT_HEADER) + 4*this->letters.size();
	int tmpsize = offset + sizeof(FNT_CHR_HEADER)*this->letters.size();
	list<FNT_CHR_HEADER*> dims;
	for (int n = 0; n < this->letters.size(); n++) {
		unsigned char *letter = this->get_letter(n);
		FNT_CHR_HEADER *dim = letter_dims(letter, this->width, this->height);
		dims.push_back(dim);
		tmpsize += dim->width*dim->height;
	}
	char *buffer = new char[tmpsize];
	memset(buffer, 0, tmpsize);
	FNT_HEADER *header = (FNT_HEADER*)buffer;
	memcpy(&header->sign, "FONT", 4);
	header->lowIndex = this->lowIndex;
	header->highIndex = this->lowIndex+this->letters.size()-1;
	header->maxWidth = this->width;
	header->maxHeight = this->height;
	list<FNT_CHR_HEADER*>::iterator dim = dims.begin();
	char *cur = buffer+sizeof(FNT_HEADER);
	for (vector<unsigned char*>::iterator letter = this->letters.begin(); letter != this->letters.end(); letter++) {
		if ((*dim)->width) {
			pack(cur, 4, "<L", offset);
			memcpy(buffer+offset, *dim, sizeof(FNT_CHR_HEADER));
			offset += sizeof(FNT_CHR_HEADER);
			char jmp = 0;
			char *comp = buffer+offset;
			for (int y = (*dim)->yOffset; y < (*dim)->yOffset+(*dim)->height; y++) {
				for (int x = (*dim)->xOffset; x < (*dim)->xOffset+(*dim)->width; x++) {
					char color = (*letter)[x+y*this->width];
					if (color == 0 && jmp < 31) {
						jmp++;
					} else {
						*comp = (jmp << 3) + (color & 0x07);
						jmp = 0;
						comp++;
						offset += 1;
					}
				}
			}
			if (jmp) {
				*comp = (jmp << 3);
				offset += 1;
			}
		}
		delete [] *dim;
		dim++;
		cur += 4;
	}
	size = offset;
	char *sized = new char[size];
	memcpy(sized, buffer, size);
	delete [] buffer;
	return sized;
}

unsigned char* FNT::get_letter(int n) {
	if (n < lowIndex || n >= lowIndex+this->letters.size()) {
		SCMSError err("Internal", "Letter index out of range (got %u, expected number in range %u-%u)", n, lowIndex, lowIndex+this->letters.size());
		throw err;
	}
	return this->letters[n-lowIndex];
}

void FNT::set_letter(int n, unsigned char *letter) {
	if (n < lowIndex || n >= lowIndex+this->letters.size()) {
		SCMSError err("Internal", "Letter index out of range (got %u, expected number in range %u-%u)", n, lowIndex, this->letters.size());
		throw err;
	}
	this->letters[n-lowIndex] = letter;
}

FNT_CHR_HEADER* FNT::letter_dims(unsigned char *letter, char width, char height) {
	FNT_CHR_HEADER *dims = new FNT_CHR_HEADER;
	dims->width = 0;
	dims->height = 0;
	dims->xOffset = width;
	dims->yOffset = height;
	for (int y = 0; y < height; y++) {
		bool empty = true;
		for (int x = 0; x < width; x++) {
			char c = letter[x+y*width];
			if (c != 0) {
				if (x < dims->xOffset)
					dims->xOffset = x;
				if (x + 1 > dims->width)
					dims->width = x + 1;
				empty = false;
			}
		}
		if (!empty) {
			if (y < dims->yOffset)
				dims->yOffset = y;
			dims->height = y;
		}
	}
	if (dims->width) {
		dims->width -= dims->xOffset;
		dims->height -= dims->yOffset - 1;
	}
	return dims;
}

void FNT::close() {
	for (vector<unsigned char*>::iterator letter = this->letters.begin(); letter != this->letters.end(); letter++)
		delete [] *letter;
	this->letters.clear();
	this->lowIndex = this->width = this->height = 0;
}